package com.gyy.socket;

import com.gyy.utils.IEEE754Utils;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
 * @Author gmd
 * @Description java socket 实现modbusTCP的读功能
 * @Date 2022-02-01 20:27:54
 */
public class SocketTcpRead {

    public static Socket socket;

    public static InputStream inputStream;

    public static OutputStream outputStream;


    /**
     * socket初始化，初始化socket、inputStream、outputStream
     */
    public static void initSocket(String host, int port) throws Exception {
        // 初始化socket
        socket = new Socket(host, port);
        // 初始化输入流
        inputStream = socket.getInputStream();
        // 初始化输出流
        outputStream = socket.getOutputStream();
    }


    /**
     * 释放资源（优雅关闭）
     */
    public static void release() throws Exception {
        // 关闭socket
        if (socket != null) {
            socket.close();
        }
        // 关闭输入流
        if (inputStream != null) {
            inputStream.close();
        }
        // 关闭输出流
        if (outputStream != null) {
            outputStream.close();
        }
    }


    /**
     * 读取线圈Coil状态值
     *
     * @param slaveId  从机ID，0-255
     * @param address  数据起始地址，下标从0开始
     * @param quantity 需要读取的数量
     */
    public static void readCoils(int slaveId, int address, int quantity) throws Exception {
        // 先初始化发送报文全部为0
        byte[] sendMsg = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        // 设置发送报文长度
        sendMsg[5] = 0x06;
        // 设置从机ID
        sendMsg[6] = (byte) slaveId;
        // 设置功能码为读取线圈状态
        sendMsg[7] = 0x01;
        // 设置起始地址
        if (address > 255) {
            sendMsg[8] = (byte) (address - 255);
            sendMsg[9] = (byte) 0xFF;
        } else {
            sendMsg[9] = (byte) address;
        }
        // 设置读取数量
        if (quantity > 255) {
            sendMsg[10] = (byte) (quantity - 255);
            sendMsg[11] = (byte) 0xFF;
        } else {
            sendMsg[11] = (byte) quantity;
        }

        // 发送请求报文
        outputStream.write(sendMsg);
        outputStream.flush();

        // 初始化响应报文
        byte[] receiveMsg = new byte[9 + ((quantity + 7) / 8)];
        // 读取响应报文，并写入数据
        inputStream.read(receiveMsg);

        // 判断报文是否合法，合法报文再做处理
        if (checkReceive(receiveMsg)) {
            // 报文长度
            int len = (int) receiveMsg[8];
            // 报文结果，它的数据从低位开始，8位一组，按顺序先后存入byte数组。详情见附注1
            StringBuilder value = new StringBuilder();
            for (int i = 0; i < len; i++) {
                if (i == len - 1) {
                    // 如果是末尾不足8位，则要根据请求数量和当前数量做比较，不足补0
                    value.insert(0, String.format("%0" + (quantity - value.length()) + "d", Integer.valueOf(Integer.toBinaryString(receiveMsg[9 + i] & 0xFF))));
                } else {
                    // 如果不是末尾不足8位，则直接按8位计算，不足补0
                    value.insert(0, String.format("%08d", Integer.valueOf(Integer.toBinaryString(receiveMsg[9 + i] & 0xFF))));
                }
            }
            // 结果翻转
            value.reverse();
            System.out.println("线圈状态（0x01），地址" + address + "至" + (address + quantity) + "的状态数值为：" + value);
        }

    }


    /**
     * 读取离散输入DiscreteInput状态值
     *
     * @param slaveId  从机ID，0-255
     * @param address  数据起始地址，下标从0开始
     * @param quantity 需要读取的数量
     */
    public static void readDiscreteInputs(int slaveId, int address, int quantity) throws Exception {
        // 先初始化发送报文全部为0
        byte[] sendMsg = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        // 设置发送报文长度
        sendMsg[5] = 0x06;
        // 设置从机ID
        sendMsg[6] = (byte) slaveId;
        // 设置功能码为读取离散输入状态
        sendMsg[7] = 0x02;
        // 设置起始地址
        if (address > 255) {
            sendMsg[8] = (byte) (address - 255);
            sendMsg[9] = (byte) 0xFF;
        } else {
            sendMsg[9] = (byte) address;
        }
        // 设置读取数量
        if (quantity > 255) {
            sendMsg[10] = (byte) (quantity - 255);
            sendMsg[11] = (byte) 0xFF;
        } else {
            sendMsg[11] = (byte) quantity;
        }

        // 发送请求报文
        outputStream.write(sendMsg);
        outputStream.flush();

        // 初始化响应报文
        byte[] receiveMsg = new byte[9 + ((quantity + 7) / 8)];
        // 读取响应报文，并写入数据
        inputStream.read(receiveMsg);

        // 判断报文是否合法，合法报文再做处理
        if (checkReceive(receiveMsg)) {
            // 报文长度
            int len = (int) receiveMsg[8];
            // 报文结果，它的数据从低位开始，8位一组，按顺序先后存入byte数组。详情见附注1
            StringBuilder value = new StringBuilder();
            for (int i = 0; i < len; i++) {
                if (i == len - 1) {
                    // 如果是末尾不足8位，则要根据请求数量和当前数量做比较，不足补0
                    value.insert(0, String.format("%0" + (quantity - value.length()) + "d", Integer.valueOf(Integer.toBinaryString(receiveMsg[9 + i] & 0xFF))));
                } else {
                    // 如果不是末尾不足8位，则直接按8位计算，不足补0
                    value.insert(0, String.format("%08d", Integer.valueOf(Integer.toBinaryString(receiveMsg[9 + i] & 0xFF))));
                }
            }
            // 结果翻转
            value.reverse();
            System.out.println("离散输入状态（0x02），地址" + address + "至" + (address + quantity) + "的状态数值为：" + value);
        }

    }


    /**
     * 读取保持寄存器HoldingRegisters数据
     *
     * @param slaveId  从机ID，0-255
     * @param address  数据起始地址，下标从0开始
     * @param quantity 需要读取的数量（注意此出的数量指的地址的数量，而不是数据的数量）附注2
     * @param dataType 数据类型（binary、int、unSign、float、double）
     */
    public static void readHoldingRegisters(int slaveId, int address, int quantity, String dataType) throws Exception {
        // 先初始化发送报文全部为0
        byte[] sendMsg = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        // 设置发送报文长度
        sendMsg[5] = 0x06;
        // 设置从机ID
        sendMsg[6] = (byte) slaveId;
        // 设置功能码为读取保持寄存器数据
        sendMsg[7] = 0x03;
        // 设置起始地址
        if (address > 255) {
            sendMsg[8] = (byte) (address - 255);
            sendMsg[9] = (byte) 0xFF;
        } else {
            sendMsg[9] = (byte) address;
        }
        // 设置读取数量
        if (quantity > 255) {
            sendMsg[10] = (byte) (quantity - 255);
            sendMsg[11] = (byte) 0xFF;
        } else {
            sendMsg[11] = (byte) quantity;
        }

        // 发送请求报文
        outputStream.write(sendMsg);
        outputStream.flush();

        // 初始化响应报文
        byte[] receiveMsg = new byte[9 + quantity * 2];

        // 读取响应报文，并写入数据
        inputStream.read(receiveMsg);

        // 判断报文是否合法，合法报文再做处理
        if (checkReceive(receiveMsg)) {
            // 报文长度
            int len = (int) receiveMsg[8];
            // 报文结果
            StringBuilder value = new StringBuilder();
            // 临时二进制变量
            StringBuilder tempBinary = new StringBuilder();

            switch (dataType) {
                // 转二进制
                case "binary": {
                    for (int i = 0; i < len; i += 2) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1));
                        value.append(tempBinary).append(" ");
                    }
                    break;
                }
                // 转有符号十进制
                case "int": {
                    for (int i = 0; i < len; i += 2) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1));
                        value.append(Integer.parseInt(tempBinary.toString(), 2)).append(" ");
                    }
                    break;
                }
                // 转无符号十进制
                case "unSign": {
                    for (int i = 0; i < len; i += 2) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1));
                        value.append(Integer.parseUnsignedInt(tempBinary.toString(), 2)).append(" ");
                    }
                    break;
                }
                // 转单精度浮点数
                case "float": {
                    for (int i = 0; i < len; i += 4) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[12 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[11 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1));
                        value.append(IEEE754Utils.binaryToFloat(tempBinary.toString())).append(" ");
                    }
                    break;
                }
                // 转双精度浮点数
                case "double": {
                    for (int i = 0; i < len; i += 8) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[16 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[15 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[14 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[13 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[12 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[11 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1));
                        value.append(IEEE754Utils.binaryToDouble(tempBinary.toString())).append(" ");
                    }
                    break;
                }
                default: {
                    System.out.println("数据类型不合法，允许的数据类型为：binary（二进制）、int（十进制有符号）、unSign（十进制无符号）、float（单精度浮点数）、double（双精度浮点数）");
                }
            }
            System.out.println("保持寄存器（0x03），地址" + address + "至" + (address + quantity) + "的数值为：" + value);
        }

    }


    /**
     * 读取输入寄存器InputRegisters数据
     *
     * @param slaveId  从机ID，0-255
     * @param address  数据起始地址，下标从0开始
     * @param quantity 需要读取的数量（注意此出的数量指的地址的数量，而不是数据的数量）附注2
     * @param dataType 数据类型（binary、int、unSign、float、double）
     */
    public static void readInputRegisters(int slaveId, int address, int quantity, String dataType) throws Exception {
        // 先初始化发送报文全部为0
        byte[] sendMsg = new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
        // 设置发送报文长度
        sendMsg[5] = 0x06;
        // 设置从机ID
        sendMsg[6] = (byte) slaveId;
        // 设置功能码为读取输入寄存器数据
        sendMsg[7] = 0x04;
        // 设置起始地址
        if (address > 255) {
            sendMsg[8] = (byte) (address - 255);
            sendMsg[9] = (byte) 0xFF;
        } else {
            sendMsg[9] = (byte) address;
        }
        // 设置读取数量
        if (quantity > 255) {
            sendMsg[10] = (byte) (quantity - 255);
            sendMsg[11] = (byte) 0xFF;
        } else {
            sendMsg[11] = (byte) quantity;
        }

        // 发送请求报文
        outputStream.write(sendMsg);
        outputStream.flush();

        // 初始化响应报文
        byte[] receiveMsg = new byte[9 + quantity * 2];

        // 读取响应报文，并写入数据
        inputStream.read(receiveMsg);

        // 判断报文是否合法，合法报文再做处理
        if (checkReceive(receiveMsg)) {
            // 报文长度
            int len = (int) receiveMsg[8];
            // 报文结果
            StringBuilder value = new StringBuilder();
            // 临时二进制变量
            StringBuilder tempBinary = new StringBuilder();

            switch (dataType) {
                // 转二进制
                case "binary": {
                    for (int i = 0; i < len; i += 2) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1));
                        value.append(tempBinary).append(" ");
                    }
                    break;
                }
                // 转有符号十进制
                case "int": {
                    for (int i = 0; i < len; i += 2) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1));
                        value.append(Integer.parseInt(tempBinary.toString(), 2)).append(" ");
                    }
                    break;
                }
                // 转无符号十进制
                case "unSign": {
                    for (int i = 0; i < len; i += 2) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1));
                        value.append(Integer.parseUnsignedInt(tempBinary.toString(), 2)).append(" ");
                    }
                    break;
                }
                // 转单精度浮点数
                case "float": {
                    for (int i = 0; i < len; i += 4) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[12 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[11 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1));
                        value.append(IEEE754Utils.binaryToFloat(tempBinary.toString())).append(" ");
                    }
                    break;
                }
                // 转双精度浮点数
                case "double": {
                    for (int i = 0; i < len; i += 8) {
                        tempBinary = new StringBuilder(Integer.toBinaryString((receiveMsg[16 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[15 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[14 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[13 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[12 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[11 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[10 + i] & 0xFF) + 0x100).substring(1)
                                + Integer.toBinaryString((receiveMsg[9 + i] & 0xFF) + 0x100).substring(1));
                        value.append(IEEE754Utils.binaryToDouble(tempBinary.toString())).append(" ");
                    }
                    break;
                }
                default: {
                    System.out.println("数据类型不合法，允许的数据类型为：binary（二进制）、int（十进制有符号）、unSign（十进制无符号）、float（单精度浮点数）、double（双精度浮点数）");
                }
            }
            System.out.println("输入寄存器（0x04），地址" + address + "至" + (address + quantity) + "的数值为：" + value);
        }

    }


    /**
     * 校验响应报文是否出错，如果出错则打印报错信息
     *
     * @return true 报文正常   false 报文错误
     */
    public static boolean checkReceive(byte[] message) {
        if ((int) message[7] < 0) {
            switch (message[8]) {
                case 0x01:
                    System.out.print("异常码：01（非法功能码，从站设备不支持此功能码）。");
                    break;
                case 0x02:
                    System.out.print("异常码：02（非法数据地址，指定的数据地址在从站设备中不存在）。");
                    break;
                case 0x03:
                    System.out.print("异常码：03（非法数据值，指定的数据超过范围或者不允许使用）。");
                    break;
                case 0x04:
                    System.out.print("异常码：04（从站设备故障，从站设备处理响应的过程中，出现未知错误等）。");
                    break;
                default:
                    System.out.print("程序异常。");

            }
            System.out.println("响应报文为：" + IEEE754Utils.bitsToHexStr(message));
            return false;

        } else {
            return true;
        }
    }


    public static void main(String[] args) throws Exception {
        initSocket("127.0.0.1", 502);

        // 读线圈数据
        readCoils(1, 0, 10);

        // 读离散输入数据
        readDiscreteInputs(2, 0, 10);

        // 读保持寄存器数据
        readHoldingRegisters(3, 0, 10, "float");

        // 读输入寄存器数据
        readInputRegisters(4, 0, 10, "float");

        release();
    }


}
